/*
	Copyright (C) 2010 Salil Bhagurkar

	This file is part of salil64

	salil64 is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	salil64 is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with salil64.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "types.h"
#include "desc.h"
#include "asm.h"

struct gdt_entry_struct
{
	u16 limit_low;           // The lower 16 bits of the limit.
	u16 base_low;            // The lower 16 bits of the base.
	u8 base_middle;         // The next 8 bits of the base.
	u8 access;              // Access flags, determine what ring this segment can be used in.
	u8 granularity;
	u8 base_high;           // The last 8 bits of the base.
} __attribute__((packed));
typedef struct gdt_entry_struct gdt_entry_t;

//GDT entry for the higher part of tss
struct gdt_entry2_struct
{
	u32 base_high32;
	u32 reserved;
} __attribute((packed));
typedef struct gdt_entry2_struct gdt_entry2_t;

struct gdt_ptr_struct
{
	u16 limit;               // The upper 16 bits of all selector limits.
	u64 base;                // The address of the first gdt_entry_t struct.
}
 __attribute__((packed));
typedef struct gdt_ptr_struct gdt_ptr_t;

#define NR_GDT_ENTRIES 7

static gdt_entry_t gdt_entries[NR_GDT_ENTRIES];

//This is used by asm code
gdt_ptr_t gdt_ptr;

static void gdt_set_gate(s32 num, u32 base, u32 limit, u8 access, u8 gran)
{
	gdt_entries[num].base_low    = (base & 0xFFFF);
	gdt_entries[num].base_middle = (base >> 16) & 0xFF;
	gdt_entries[num].base_high   = (base >> 24) & 0xFF;

	gdt_entries[num].limit_low   = (limit & 0xFFFF);
	gdt_entries[num].granularity = (limit >> 16) & 0x0F;

	gdt_entries[num].granularity |= gran & 0xF0;
	gdt_entries[num].access      = access;
}

// A struct describing a Task State Segment.
struct tss_entry_struct
{
	u32 reserved0;
	u32 rsp0L;
	u32 rsp0H;
	u32 rsp1L;
	u32 rsp1H;
	u32 rsp2L;
	u32 rsp2H;
	u32 reserved1;
	u32 reserved2;
	u32 ist1L;
	u32 ist1H;
	u32 ist2L;
	u32 ist2H;
	u32 ist3L;
	u32 ist3H;
	u32 ist4L;
	u32 ist4H;
	u32 ist5L;
	u32 ist5H;
	u32 ist6L;
	u32 ist6H;
	u32 ist7L;
	u32 ist7H;
	u32 reserved3;
	u32 reserved4;
	u16 reserved6;
	u16 iomap_base;
} __attribute__((packed));

typedef struct tss_entry_struct tss_entry_t;

tss_entry_t tss_entry;

// Initialise our task state segment structure.
static void write_tss(u32 num, u64 rsp0)
{
	// Firstly, let's compute the base and limit of our entry into the GDT.
	u64 base = (u64) &tss_entry;
	u64 limit = base + sizeof(tss_entry);

	// Now, add our TSS descriptor's address to the GDT.
	//First descriptor part for the TSS
	gdt_set_gate(num, (u32)base, (u32)limit, 0xE9, 0x00);
	gdt_entry2_t *entry2 = (gdt_entry2_t *)(&gdt_entries[num + 1]);
	entry2->base_high32 = (u32)(base >> 32);
	entry2->reserved = 0;

	// Ensure the descriptor is initially zero.
	memset(&tss_entry, 0, sizeof(tss_entry));

	tss_entry.rsp0L = (u32)rsp0;
	tss_entry.rsp0H = (u32)(rsp0 >> 32);

}

static void init_gdt()
{
	gdt_ptr.limit = (NR_GDT_ENTRIES * sizeof(gdt_entry_t)) - 1;
	gdt_ptr.base = (unsigned long)&gdt_entries[0];

	gdt_set_gate(0, 0, 0, 0, 0);
	gdt_set_gate(1, 0, 0xffffffff, 0x9a, 0x20);
	gdt_set_gate(2, 0, 0xffffffff, 0x92, 0x20);
	gdt_set_gate(3, 0, 0xffffffff, 0xfa, 0x20);
	gdt_set_gate(4, 0, 0xffffffff, 0xf2, 0x20);
	write_tss(5, 0x200000);
}


static struct IdtEntry idtEntries[256];

static struct DescPtr idtDesc = {.size = 256 * sizeof(struct IdtEntry), .address = (unsigned long)&idtEntries[0]};


void init_tables()
{
	unsigned long i;
	for(i = 0; i < 256; i++) {
		packIdtEntry(&idtEntries[i], GATE_INTERRUPT, (unsigned long)_intHandler, 3, 0);
	}

	loadIdt(&idtDesc);

	init_gdt();

	_gdtFlush();
	_tssFlush();
}
